20-2 阞讀

在一般電腦平台上,常見音訊檔案的附檔名有 .wav、.au、.mp3、.aif等等,我們都可以使用 MATLAB 來直接讀取這幾種檔案,所用的指令是audioread。例如,若要讀取本章範例程式目錄內的檔案 welcome.wav、畫出音訊的波形並播放出此音訊,可使用下列程式:

Example 1: 20-音訊讀寫、錄製與播放/audioRead01.m[y, fs]=audioread('welcome.wav'); sound(y, fs); % 播放此音訊 time=(1:length(y))/fs; % 時間軸的向量 plot(time, y); % 畫出時間軸上的波形

在上例中,fs 是取樣頻率(sample rate),其值為 11025,代表當初在錄製這個音訊檔案時,每秒鐘會記錄下11025個聲音訊號的取樣值。而 y 是聲音訊號的向量,使用 sound(y, fs) 可播放出此音訊,所以你會聽到我的聲音「歡迎光臨」。time 則是對應在在時間軸的向量,因此將 y 對 time 做圖,就得到在時間軸上面的音訊波形。

Hint
在上述範例中,音訊第一點所對應的時間是1/fs,但我們也可以改成對應時間是0,此時對應的時間向量應該為 time=(0:length(y)-1)/fs。

事實上在錄製音訊檔案時,每一個取樣點大部分都是由8或16個位元(bits)所代表,若要知道 welcome.wav 的取樣點是由多少個位元來表示,可使用 audioinfo(‘welcome.wav’) 來回傳各種相關資訊,範例如下:

Example 2: 20-音訊讀寫、錄製與播放/audioInfo01.mfileName='welcome.wav'; info=audioinfo(fileName); fprintf('檔案名稱 = %s\n', info.Filename); fprintf('壓縮方式 = %s\n', info.CompressionMethod); fprintf('通道個數 = %g 個\n', info.NumChannels); fprintf('取樣頻率 = %g Hz\n', info.SampleRate); fprintf('取樣點總個數 = %g 個\n', info.TotalSamples); fprintf('音訊長度 = %g 秒\n', info.Duration); fprintf('取樣點解析度 = %g 位元/取樣點\n', info.BitsPerSample); 檔案名稱 = D:\users\jang\books\matlabProgramming4beginner\example\20-音訊讀寫、錄製與播放\welcome.wav 壓縮方式 = Uncompressed 通道個數 = 1 個 取樣頻率 = 11025 Hz 取樣點總個數 = 16001 個 音訊長度 = 1.45134 秒 取樣點解析度 = 8 位元/取樣點

Hint
對於不同類別的音檔,audioinfo 所回傳的欄位可能有所不同,讀者可以是看看另一個範例 audioInfo03.m,來看看對於 mp3 檔案的回傳結果。

讀者可能會好奇,為什麼在 welcome.wav 音訊檔案中,每一個取樣點用 8 個位元來表示,但是從音訊波形來看,每一點的值都介於 –1 和 1 之間,這中間是如何轉換的?首先要知道 wav 檔案的 8 位元是以 unsigned integer(不帶符號的整數,即正整數)的方式來儲存,因此所能表示的數值是介於 0 和 255 (2^8-1) 之間,MATLAB 再將此值設定至變數 y 時,會自動將其數值調整至介於 –1 和 1 之間,因此若要將 MATLAB 讀出之數值轉回原先 8 位元所表示之數值,只要將變數 y 乘以 128,再加上 128,就可以得到原先的整數值,例如:

Example 3: 20-音訊讀寫、錄製與播放/audioRead03.mfileName='welcome.wav'; [y, fs]=audioread(fileName); info=audioinfo(fileName); nbits=info.BitsPerSample; y0=y*(2^nbits/2)+(2^nbits/2); % y0 是原先儲存在音訊檔案中的值 difference=sum(abs(y0-round(y0))) difference = 0

在上例中,變數 difference 的值是零,代表 y0 的值完全是整數。此外,為了增加程式碼的通用性,我們用 2^nbits/2,而不直接使用128。

MATLAB 對於音檔資料的正規化方式,可以列表如下:

音訊檔案內部儲存方式 數值範圍 MATLAB正規化方式
Unsigned 8-bit integer (uint8) y $\in$ [0, $2^8-1$] (y-$2^7$)/$2^7$
Signed 16-bit integer (int16) y $\in$ [$-2^{15}$, $2^{15}-1$] y/$2^{15}$
Signed 32-bit integer (int32) y $\in$ [$-2^{31}$, $2^{31}-1$] y/$2^{31}$

audioread 也可以讀取雙聲道或立體聲(Stereo)的音訊檔案,此時傳回的變數為具有兩直行的陣列,每一直行代表一個聲道的音訊,例如:

Example 4: 20-音訊讀寫、錄製與播放/audioRead04.mfileName='flanger.wav'; [y, fs]=audioread(fileName); % 讀取音訊檔 sound(y, fs); % 播放音訊 left=y(:,1); % 左聲道音訊 right=y(:,2); % 右聲道音訊 subplot(2,1,1), plot((1:length(left))/fs, left); subplot(2,1,2), plot((1:length(right))/fs, right);

此範例會讀取雙聲道的音訊檔 flanger.wav,播放此雙聲道的音訊,並畫出兩個聲道的音訊波形。由於左右聲道的音量(即波形的震幅)相互消長,因此播放出來的效果,感覺好像音源在左右聲道之間游移。(小問題:給你一個單聲道的聲音訊號,你如何使用 MATLAB 對此音訊進行處理,最後產生原音訊在雙聲道之間游移的效果,如同此範例一般?)

如果音訊檔案很大,無法一次讀入記憶體,我們也可以使用 audioread 來讀出音訊檔的其中一部份,例如:

Example 5: 20-音訊讀寫、錄製與播放/audioRead05.m[y,fs]=audioread('welcome.wav', [4001 5000]); % 讀取音訊檔第4001至5000點 plot(y)

這一段波形代表「歡迎光臨」中的「迎」剛開始發音的波形。由上圖可看出,聲波的外型是很類似一個週期波,其震幅大小就代表音量大小,而其週期的倒數就是「基本頻率」。以上述圖形而言,1000個取樣點大概包含了10個週期,所以每個週期大約包含 100點,對應的時間是 100/fs = 100/11025 = 0.0091秒 = 9.1 ms,而基本頻率則是 1/0.0091 = 110.25 Hz,換算成鋼琴的按鍵,非常接近於中央 la (對應頻率是 440 Hz)降16度(兩個全音階)所得到的音,也就是下圖中的 A4(左邊數來第 5 個白鍵)。


300

Hint
若要知道每個鋼琴按鍵的聲音高低,可以嘗試這個網頁:「http://www.oliphaunts.com/pianoroll-js」。

人耳對聲音高低的感覺,是與基本頻率的對數成正比。以鋼琴的鍵盤而言,中央 la 的頻率是 440 Hz (Hertz),高八度的中央 la 則是 880 Hz,低八度的中央 la 則是 220 Hz。鋼琴中的每一個全音階包含7個白鍵與5個黑鍵,共有 12 個鍵,代表 12 個半音(semitones),以 MIDI 的標準而言,中央 la 的半音值是 69,對應頻率是 440 Hz,因此半音和頻率之間的轉換程式可寫成下列公式: $$semitone=69+12\log_2\left(\frac{Hz}{440}\right).$$

由一段聲音來求取其音高,在音訊處理上稱為音高追蹤(Pitch Tracking),是音訊處理很重要的一環,相關應用有語音合成、音調辨識、哼唱選歌等。

前面說明過,audioread 會將讀到的音訊壓縮在 -1 和 +1 之間,但如果你想讀取原來音訊儲存在音訊檔案中的原始資料,也可以,範例如下:

Example 6: 20-音訊讀寫、錄製與播放/audioRead06.m[y, fs]=audioread('welcome.wav', [1, inf], 'native'); p=audioplayer(y, fs); play(p); % 播放此音訊 time=(1:length(y))/fs; % 時間軸的向量 plot(time, y); % 畫出時間軸上的波形 class(y) % 顯示 y 的資料型態 ans = uint8

由上例可以看出,當我們加入 'native' 當成 audioread 的第三個參數時,所讀出的音訊就會是以原來檔案內的資料型態為準,因此本例中的音訊資料 y 的資料型態為 uint8,同時畫出的波形的震幅範圍也不再限於 -1 和 +1 之間。


MATLAB程式設計:入門篇